<?php
class Myworker
{
    public $worker_num = 10;
    public $pid_file;
    public $out_file;
    public $workers = [];
    public $workerFun;

    public function __construct($pid_file, $worker_num,$workerFun,$out_file='/dev/null')
    {
        $this->pid_file = $pid_file;
        $this->worker_num = $worker_num;
        $this->out_file=$out_file;
        if(is_callable($workerFun)){
            $this->workerFun=$workerFun;
        }else{
            exit($workerFun." is not callbale");
        }

    }



    public function run()
    {
        global $argv;
        echo '###' . posix_getpid() . '###' . PHP_EOL;
        $cmd = $argv[1] ?? '';
        switch ($cmd) {
            case 'start':
                if ($this->getPidFile()) {
                    die("Already runing! " . PID_FILE . PHP_EOL);
                }
                $this->daemon();
                $master_pid = posix_getpid();
                file_put_contents($this->pid_file, $master_pid);
                for ($i = 0; $i < $this->worker_num; $i++) {
                    $this->fork();
                }
                $this->waiting();
                break;
            case 'reload':
                $master_pid = $this->getPidFile();
                if (!$master_pid) {
                    die(PID_FILE . " invalid");
                }
                exec("ps --ppid {$master_pid}|awk '/[0-9]/{print $1}'|xargs", $output, $status);
                if ($status == 0) {
                    $child_arr = explode(' ', current($output));
                    foreach ($child_arr as $id) {
                        posix_kill($id, SIGTERM);
                    }
                }

                exit();
            case 'stop':
                $master_pid = $this->getPidFile();
                if (!$master_pid) {
                    die($this->pid_file . " invalid");
                }

                exec("ps --ppid {$master_pid}|awk '/[0-9]/{print $1}'|xargs", $output, $status);
                //先杀master，省得杀woker时 主进程会自动补上
                if ($r = posix_kill($master_pid, SIGKILL)) {
                    echo 'master ' . $master_pid . ' exit successfully!' . PHP_EOL;
                    unlink($this->pid_file);
                } else {
                    //查看进程是否在运行
                    exec("ps --pid {$master_pid}|grep -v CMD|awk '{print $1}'", $output, $status);
                    $get_master_id = current($output);
                    if (!$get_master_id) {
                        @unlink($this->pid_file);
                    }
                    echo 'master ' . $master_pid . ' exit Fail! deleted pid file' . var_export($r, true) . PHP_EOL;
                }


                if ($status == 0) {
                    $child_arr = array_filter(explode(' ', current($output)));
                    foreach ($child_arr as $id) {
                        if (posix_kill($id, SIGKILL)) {
                            echo 'child ' . $id . ' exit successfully!' . PHP_EOL;
                        }
                    }
                }


                exit();
            default:
                die ("please input command" . PHP_EOL);
        }
    }


    public function daemon()
    {
        $pid = pcntl_fork();
        if ($pid < 0) die("fork err");
        if ($pid == 0) {
            if (posix_setsid() <= 0) {
                exit("setsid err!");
            }

            $pid=pcntl_fork();
            if($pid<0){
                exit('fork err!');
            }elseif ($pid>0){
                //主进程退出
                exit();
            }

            if (chdir('/') === false) {
                die("change dir err");
            }
            umask(0);

            $this->resetStd();


        } else {
            exit();
        }
    }

    public function fork()
    {

        $pid = pcntl_fork();
        if ($pid < 0) die("fork err");
        if ($pid == 0) {
            $child_pid = posix_getpid();
            call_user_func($this->workerFun,$this);
        } else {
            $parent_pid = posix_getpid();
            $this->workers[$pid] = $pid;
        }
    }

    public function waiting()
    {

        while (count($this->workers)) {
            if (($exit_id = pcntl_wait($status)) > 0) {
                $signo = pcntl_wtermsig($status);
                unset($this->workers[$exit_id]);
            }
            if (count($this->workers) < $this->worker_num) {
                $this->fork();
            }
        }
    }

//验证是否存而且合法
    public function getPidFile()
    {
        if (file_exists($this->pid_file)) {
            $master_pid = file_get_contents($this->pid_file);
            if ($master_pid) {
                return $master_pid;
            }
        }
        return false;

    }
    private function resetStd()
    {
        global $stdin, $stdout, $stderr;

        //关闭打开的文件描述符
        fclose(STDIN);
        fclose(STDOUT);
        fclose(STDERR);

        if ($this->out_file != '/dev/null' && !file_exists($this->out_file)) {
            touch($this->out_file);
            chmod($this->out_file, 0755);
            $this->out_file = realpath($this->out_file);
        }

        $stdin  = fopen($this->out_file, 'r');
        $stdout = fopen($this->out_file, 'a');
        $stderr = fopen($this->out_file, 'a');
    }
}